
#import sys
#if sys.version_info < (2,6):
#	raise ImportError('this module requires Python version at least 2.6')
from datetime import datetime, time
from types import FunctionType

#from etreestore import Document, Element

# ===========================================================================

# Metaclass types

class ModelType(type):

	_clsmap = {}

	def __new__(mcls, name, bases, dict):
#		dict['__slots__'] = ()
		dict.setdefault('__slots__', ())
		cls = type.__new__(mcls, name, bases, dict)

#		if '__metaclass__' in dict:
#			baseinit = bases[0].__init__
#			def __init__(self, *args):
#				baseinit(self, *args)
#				self.__class__ = ModelType._registry.get(self.tag, cls)
#			bases[0].__init__ = __init__

		if '_attrs_' in dict:
			for name, kind in cls._attrs_.iteritems():
				if type(kind) is tuple:
					kind, attrs = kind[0], kind[1:]
				else:
					attrs = (name,)
				fget, fset = ModelType.accessors(cls, kind, *attrs)
				setattr(cls, name, property(fget, fset))

		if '_name_' in dict:
			ModelType._clsmap[cls._name_] = cls

		if '_child_' in dict:
			childtags = set(cls._child_)
			def __iter__(self):
				for item in self._iterchildren(childtags):
					yield ModelType._clsmap[item.name](item)
		else:
			def __iter__(self):
				if False: yield  # zero-length iterator
		cls.__iter__ = __iter__

		return cls

	@staticmethod
	def accessors(cls, kind, *attrs):
		if hasattr(kind, '_basename_'):
			name = kind._basename_
			attrs = (kind,) + attrs
		else:
			name = kind.__name__
		get = getattr(cls, '_get_' + name)
		set = getattr(cls, '_set_' + name)
		return ((lambda self: get(self, *attrs)),
				(lambda self, value: set(self, value, *attrs)))

class AttrType(type):

	def __init__(cls, name, bases, dict):
		type.__init__(cls, name, bases, dict)
		if '__metaclass__' in dict:
			cls._basename_ = name
		else:
			for name, val in dict.iteritems():
				if type(val) is FunctionType:
					setattr(cls, name, staticmethod(val))
			cls._initsubcls()

	def __call__(cls):
		raise TypeError('cannot create %r instances' % cls.__name__)

# ===========================================================================

# Mushclient data representation

class Trinary(int):
	__slots__ = ()
	def __new__(cls, val):
		if not isinstance(val, int): raise TypeError('Trinary() argument must be an int, not %r' % val.__class__.__name__)
		return int.__new__(Trinary, val if 0 <= val <= 2 else 2)
	def __repr__(self):
		if self == 0: return 'False'
		elif self == 1: return 'True'
		else: return 'Undetermined'

Undetermined = Trinary(2)

class Choice(object):
	__metaclass__ = AttrType
	@classmethod
	def _initsubcls(cls):
		cls.getmap = dict((val, idx) for idx, (val, lbl) in enumerate(cls._choices_))
		cls.setmap = dict((idx, val) for idx, (val, lbl) in enumerate(cls._choices_))
		cls.choices = [lbl for val, lbl in cls._choices_]

class Subitem(object):
	__metaclass__ = AttrType
	@classmethod
	def _initsubcls(cls):
		cls.accessorlst = [(attr, ModelType.accessors(ModelBase, kind, attr))
							for attr, kind in cls._attrs_.iteritems()]

DATETIME_FORMAT = '%Y-%m-%d %H:%M:%S'
NOT_FOUND = -1

class ModelBase():

	__slots__ = '_item'
	__metaclass__ = ModelType

	def __init__(self, item):
		self._item = item

	def _get_bool(self, attr):
		return self._item[attr] in ('y', 'Y', '1')

	def _set_bool(self, value, attr):
		if value: self._item[attr] = 'y'
		else: del self._item[attr]

	def _get_str(self, attr):
		return self._item[attr] or ''

	def _set_str(self, value, attr):
		if value: self._item[attr] = value
		else: del self._item[attr]

	def _get_int(self, attr):
		value = self._item[attr]
		if value:
			try: return int(value)
			except ValueError: pass
		return 0

	def _set_int(self, value, attr):
		if value: self._item[attr] = '%d' % value
		else: del self._item[attr]

	def _get_float(self, attr):
		value = self._item[attr]
		if value:
			try: return float(value)
			except ValueError: pass
		return 0.0

	def _set_float(self, value, attr):
		if value: self._item[attr] = '%.2f' % value
		else: del self._item[attr]

	def _get_datetime(self, attr):
		value = self._item[attr]
		if value:
			try: return datetime.strptime(value, DATETIME_FORMAT)
			except ValueError: pass
		return None

	def _set_datetime(self, value, attr):
		if value: self._item[attr] = value.strftime(DATETIME_FORMAT)
		else: del self._item[attr]

	def _get_time(self, hrsattr, minattr, secattr):
		hrs = self._get_int(hrsattr)
		min = self._get_int(minattr)
		sec = self._get_float(secattr)
		try: return time(hrs, min, int(sec), int(sec*1000000 - int(sec)*1000000))
		except ValueError: return time()

	def _set_time(self, value, hrsattr, minattr, secattr):
		if value:
			self._item[hrsattr] = '%d' % value.hour
			self._item[minattr] = '%d' % value.minute
			self._item[secattr] = '%.2f' % (value.second + value.microsecond/1000000.0)
		else:
			del self._item[hrsattr]
			del self._item[minattr]
			del self._item[secattr]

	def _get_Trinary(self, valattr, flgattr):
		return self._get_bool(valattr) if self._get_bool(flgattr) else Undetermined

	def _set_Trinary(self, value, valattr, flgattr):
		value = Trinary(value)
		self._set_bool(flgattr, value != Undetermined)
		self._set_bool(valattr, value == True)

	def _get_Choice(self, choice, valattr, flgattr=None):
		flag = not flgattr or self._get_bool(flgattr)
		value = flag and self._get_int(valattr)
		return choice.getmap.get(value, NOT_FOUND)

	def _set_Choice(self, value, choice, valattr, flgattr=None):
		value = choice.setmap.get(value, 0)
		if flgattr: self._set_bool(flgattr, value != 0)
		self._set_int(valattr, value)

	def _get_Subitem(self, subitem, tag):
		child = self._getchild(tag)
		kwargs = dict((attr, fget(child))
				for attr, (fget, fset) in subitem.accessorlst)
		return subitem.get(**kwargs)

	def _set_Subitem(self, value, subitem, tag):
		child = self._getchild(tag) # need to know if child should be created
		kwargs = subitem.set(value)
		if kwargs:
			for attr, (fget, fset) in subitem.accessorlst:
				fset(child, kwargs[attr])
		else:
			self._delchild(child) # this is probably too simplistic

# ===========================================================================

# Mushclient data model

class Subtext(Subitem):
	_attrs_ = dict(_=str)
	def get(_):
		return _
	def set(value):
		return dict(_=value) if value else None

class Trimtext(Subitem):
	_attrs_ = dict(_=str, trim=bool)
	def get(_, trim):
		if trim: _ = _.strip()
		return _
	def set(value):
		if value: value = '\n%s\n' % value
		return dict(_=value, trim=bool(value))

class Sendto(Choice):
	_choices_ = [(0,  'world'),
				 (8,  'world (queued)'),
				 (13, 'world (immediate)'),
				 (1,  'command'),
				 (2,  'output'),
				 (3,  'status'),
				 (9,  'variable'),
				 (10, 'execute'),
				 (11, 'speedwalk'),
				 (12, 'script'),
				 (14, 'script (omit out)'),
				 (4,  'notepad (new)'),
				 (5,  'notepad (append)'),
				 (7,  'notepad (replace)'),
				 (6,  'log file')]

class Matchcol(Choice):
	_choices_ = [(0,  '(any)'),
				 (8,  'black'),
				 (9,  'red'),
				 (10, 'green'),
				 (11, 'yellow'),
				 (12, 'blue'),
				 (13, 'magenta'),
				 (14, 'cyan'),
				 (15, 'white')]

class Custcol(Choice):
	_choices_ = [(0,  '(no change)')] + \
				[(x,  'custom%d'%x) for x in range(1, 17)] + \
				[(17, 'other...')]

class Whatcol(Choice):
	_choices_ = [(0, 'both'), (1, 'text'), (2, 'back')]

class Copyarg(Choice):
	_choices_ = [(0, 'none')] + [(x, str(x)) for x in range(1, 11)]

class MuclientItem(ModelBase):
	_name_ = 'muclient'
	_child_ = ['plugin', 'include', 'script', 'variables', 
			   'aliases', 'triggers', 'timers']

class PluginItem(ModelBase):
	_name_ = 'plugin'
	_attrs_ = dict(name=str, author=str, id=str, language=str, purpose=str,
				   date_written=datetime, date_modified=datetime, requires=float,
				   version=str, save_state=bool, description=Trimtext)

class IncludeItem(ModelBase):
	_name_ = 'include'
	_attrs_ = dict(name=str, plugin=bool)

class ScriptItem(ModelBase):
	_name_ = 'script'
	_attrs_ = dict(value=(str, None))

class VariableGroup(ModelBase):
	_name_ = 'variables'
	_child_ = ['variable']
	_attrs_ = dict(muclient_version=float, world_file_version=int, date_saved=datetime)

class VariableItem(ModelBase):
	_name_ = 'variable'
	_attrs_ = dict(name=str, value=(str, None))

class AliasGroup(ModelBase):
	_name_ = 'aliases'
	_child_ = ['alias']
	_attrs_ = dict(muclient_version=float, world_file_version=int, date_saved=datetime)

class TriggerGroup(ModelBase):
	_name_ = 'triggers'
	_child_ = ['trigger']
	_attrs_ = dict(muclient_version=float, world_file_version=int, date_saved=datetime)

class TimerGroup(ModelBase):
	_name_ = 'timers'
	_child_ = ['timer']
	_attrs_ = dict(muclient_version=float, world_file_version=int, date_saved=datetime)

class DetailItemBase(ModelBase):
	_attrs_ = dict(name=str, group=str, enabled=bool, match=str, sequence=int,
				   regexp=bool, ignore_case=bool, keep_evaluating=bool,
				   send_to=Sendto, script=str, variable=str, send=Subtext,
				   one_shot=bool, omit_from_log=bool, omit_from_output=bool)

class AliasItem(DetailItemBase):
	_name_ = 'alias'
	_attrs_ = dict(match=str, sequence=int, regexp=bool, ignore_case=bool,
				   keep_evaluating=bool, menu=bool, omit_from_history=bool,
				   expand_variables=bool, echo_alias=bool)

class TriggerItem(DetailItemBase):
	_name_ = 'trigger'
	_attrs_ = dict(match=str, sequence=int, regexp=bool, ignore_case=bool,
				   keep_evaluating=bool, repeat=bool, multi_line=bool, lines_to_match=int,
				   text_colour=(Matchcol, 'text_colour', 'match_text_colour'),
				   back_colour=(Matchcol, 'back_colour', 'match_back_colour'),
				   match_bold=(Trinary, 'bold', 'match_bold'),
				   match_ital=(Trinary, 'italic', 'match_italic'),
				   match_invr=(Trinary, 'inverse', 'match_inverse'),
				   expand_variables=bool, lowercase_wildcard=bool,
				   custom_colour=Custcol, colour_change_type=Whatcol,
				   other_text_colour=str, other_back_colour=str,
				   make_bold=bool, make_italic=bool, make_underline=bool,
				   sound=str, sound_if_inactive=bool, clipboard_arg=Copyarg)

class TimerItem(DetailItemBase):
	_name_ = 'timer'
	_attrs_ = dict(active_closed=bool, at_time=bool, 
				   time=(time, 'hour', 'minute', 'second'),
				   offset=(time, 'offset_hour', 'offset_minute', 'offset_second'))

#				   hour=int, minute=int, second=float,
#				   offset_hour=int, offset_minute=int, offset_second=float)

DocItems = (MuclientItem, ScriptItem)